package com.runa.monitor.platinterface.netservice.connect;

import com.alibaba.fastjson.JSON;
import com.runa.monitor.platinterface.base.BaseContainer;
import com.runa.monitor.platinterface.netservice.connect.commstatus.CommStatus;
import com.runa.monitor.platinterface.netservice.equipment.PlcService;
import com.runa.protocol.dto.DtuHDRequestData;
import com.runa.protocol.dto.IInterData;
import com.runa.protocol.dto.IRequestData;
import com.runa.protocol.dto.IResponseData;
import com.runa.protocol.util.ByteUtil;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;

import java.util.HashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentSkipListMap;

/**
 * @author zhuj
 */
@Slf4j
public class TellSession implements Session {

    private Channel channel;

    private Map<Integer, Queue<IRequest<IRequestData>>> orderRequestQueue = new ConcurrentSkipListMap<>();

    private IRequest currRequest;

    private Set<PlcService> services = new HashSet<>();

    private volatile boolean key = true;

    private int timeOut;

    private long prevTime;

    public TellSession(int timeout) {
        this.timeOut = timeout == 0 ? 1000 : timeout;
    }

    public void corrPlcService(PlcService service) {
        services.add(service);
    }

    @Override
    public void linked(Channel channel) {
        if (channel != null && channel.isActive() && this.channel != channel){
            if (this.channel != null){
                BaseContainer.removeSession(this.channel.id().asLongText());
                this.channel.close();
            }
            this.channel = channel;
            BaseContainer.putSession(channel.id().asLongText(), this);
            setOnlineStatus(true);
        }
    }

    public void pushRequest(IRequest request) {
        Queue<IRequest<IRequestData>> requestQueue = orderRequestQueue.get(request.flag(0));
        if (requestQueue == null) {
            requestQueue = new ConcurrentLinkedQueue<>();
            orderRequestQueue.put(request.flag(0), requestQueue);
        }
        requestQueue.offer(request);
    }

    @Override
    public void setOnlineStatus(boolean status) {
        for (PlcService service : services) {
            service.updateCommStatus(status);
        }
    }

    @Override
    public boolean isOnline() {
        return channel != null && channel.isActive();
    }

    @Override
    public void requesting() {
        if (isOnline()) {
            if (key) {
                currRequest = null;
                for (Queue<IRequest<IRequestData>> queue : orderRequestQueue.values()) {
                    if (!queue.isEmpty()) {
                        currRequest = queue.poll();
                        break;
                    }
                }
                if (currRequest != null) {
                    Object requestData = currRequest.getRequestData();
                    log.info("requesting {} {}", JSON.toJSONString(requestData), requestData.getClass().getName());
                    if (requestData instanceof DtuHDRequestData) {
                        DtuHDRequestData dtuHDRequestData = (DtuHDRequestData)requestData;
                        log.info("dtuHDRequestData {}", JSON.toJSONString(dtuHDRequestData));
                        log.info("发送: {}", ByteUtil.byteToHexString((dtuHDRequestData.getData())));
                    }
                    channel.writeAndFlush(requestData);
                    prevTime = System.currentTimeMillis();
                    key = false;
                }
            } else if (System.currentTimeMillis() - prevTime > timeOut) { //超时机制
                reset(CommStatus.TimeOut);
            }
        }
    }

    private void reset(CommStatus status) {
        key = true;
        Queue<IRequest<IRequestData>> requestQueue = orderRequestQueue.get(currRequest.flag(0));
        requestQueue.removeIf(req -> req.flag(1) == currRequest.flag(1));
        currRequest.cancel(status);
    }

    @Override
    public void receiving(IResponseData data) {
        log.info("key: {}", key);
        if (!key) {
            IResponse<IInterData> response = currRequest.getResponse();
            response.setResponseData(data);
            log.info("check: {}", currRequest.check());
            if (currRequest.check()) {
                key = true;
                response.doReceive();
            }
        }
    }

    @Override
    public boolean close() {
        try {
            if (channel != null && channel.isActive()) {
                channel.close().sync();
            }
            channel = null;
        } catch (Exception e) {
            return false;
        }
        return true;
    }

}
